#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <dataset.h>

#include "analyzer.h"
#include "output.h"
#include "filter.h"

int parse_option(int argc, char **argv, int i, AnalyzerParam *param) {
	if (strcmp(argv[i], "-q") == 0) {
		i++;
		if (i >= argc) {
			param->quote = QUOTE_C;
			return i;
		} else if (strcmp(argv[i], "csv") == 0)
			param->quote = QUOTE_CSV;
		else if (strcmp(argv[i], "c") == 0)
			param->quote = QUOTE_C;
		else {
			param->quote = QUOTE_C;
			return i;
		}
		i++;
	} else if (strcmp(argv[i], "-h") == 0) {
		param->show_help = TRUE;
		i++;
	} else {
		fprintf(stderr, "Unrecognized parameter: %s\n", argv[i]);
		return -1;
	}
	return i;
}

void parse_control(const char *str, AnalyzerParam *param) {
	const char *p = str;
//printf("Control: %s\n", str);
	while (*p == ' ')
		p++;
	if (*p != '{') {
		fprintf(stderr, "Invalid control string at: \"%s\", expected '{'.\n",
				p);
		exit(1);
	}
	if (*++p != ':')
		p = filter_parse_condition(p, &param->condition);
	if (p == NULL || *p != ':') {
		fprintf(stderr, "Invalid control string at: \"%s\", expected ':'.\n",
				p);
		exit(1);
	}
	if (*++p != '}')
		p = output_parse_format(p, param->format);
	if (p == NULL || *p != '}') {
		fprintf(stderr, "Invalid control string at: \"%s\", expected '}'.\n",
				p);
		exit(1);
	}
//printf("control parsed \n");
//FormatToken *e;
//list_for_each_entry(e, &param->format, list) {
//	printf("TOKEN: %d,\t%s\n", e->place_holder, e->param);
//}
}

void parse_source(char *arg, AnalyzerParam *param) {
	if (strlen(arg) < 1)
		return;
	InputSource *s = (InputSource *) awl_alloc(sizeof(InputSource));
	s->fp = fopen(arg, "r");
	if (!(s->fp)) {
		fprintf(stderr, "Invalid input source: %s\n", arg);
		awl_free(s);
		exit(-1);
	}
	push(param->sources, s);
}

static AnalyzerParam * check_param(AnalyzerParam *param) {
	if (count_list(param->format) < 1) {
		return NULL;
	}
	if (count_list(param->sources) < 1) {
		InputSource *is = (InputSource *) awl_alloc(sizeof(InputSource));
		is->fp = stdin;
		push(param->sources, is);
	}
	return param;
}

void init_param(AnalyzerParam *param) {
	param->show_help = FALSE;
	param->quote = QUOTE_NONE;
	param->format = create_list();
	param->sources = create_list();
}

void destroy_param(AnalyzerParam *param) {
	InputSource *s;
	move_head(param->sources);
	while ((s = (InputSource *) next(param->sources)) != NULL) {
		if (s->fp != NULL)
			fclose(s->fp);
		awl_free(s);
	}
	destroy_list(param->sources);

	FormatToken *ft;
	move_head(param->format);
	while ((ft = (FormatToken *) next(param->format)) != NULL) {
		awl_free(ft);
	}
	destroy_list(param->format);

	if (param->condition != NULL) {
		exp_destroy(param->condition);
	}
	awl_free(param);
}

AnalyzerParam * parse_param(const int argc, char **argv) {
	int i = 1;
	AnalyzerParam * param = (AnalyzerParam *) awl_alloc(sizeof(AnalyzerParam));
	init_param(param);

	while (i < argc) {
		if (argv[i][0] != '-')
			break;
		i = parse_option(argc, argv, i, param);
		if (i < 0) {
			destroy_param(param);
			return NULL;
		}
	}

	if (i < argc)
		parse_control(argv[i++], param);

	while (i < argc)
		parse_source(argv[i++], param);

	if (check_param(param) == NULL) {
		destroy_param(param);
		param = NULL;
	}

	return param;
}

int analyzer_init(AnalyzerParam *param) {
	return 0;
}

static void analyse_file(FILE *fp, AnalyzerParam *param) {
	char line[MAX_LOG_ENTRY_LEN];
	HttpLogInfo logInfo;

	while (fgets(line, MAX_LOG_ENTRY_LEN, fp) != NULL) {
		if (parse_http_log_entry(&logInfo.logEntry, line) != 0) {
			discard_http_log_entry(&logInfo.logEntry);
			continue;
		}
		if (filter_check(&logInfo, param->condition))
			output_print(stdout, &logInfo, param);
		discard_http_log_entry(&logInfo.logEntry);
	}
}

static void analyzer_analyse(AnalyzerParam *param) {
	InputSource *is;
	move_head(param->sources);
	while ((is = next(param->sources)) != NULL) {
		analyse_file(is->fp, param);
	}
}

static void analyzer_destroy(AnalyzerParam *param) {
}

static void analyzer_usage(const char *cmd) {
	printf(
			"\nUsage: %s [options] '{<filter>:<output format>}' [log file 1] ...\n"
					"\noptions:\n"
					"	-h		show this help.\n"
					"	-q c|csv	encode output string as c code(default) or CSV data.\n"
					"			NOTE: remember to add quotes and commas to the fields yourself.\n"
					"\nfilter variables:\n"
					"	h	host ip\n"
					"	i	identity\n"
					"	u	userid\n"
					"	t	time\n"
					"	ty	year part of time\n"
					"	tM	month part of time\n"
					"	td	date part of time\n"
					"	tH	hour part of time\n"
					"	tm	minute part of time\n"
					"	ts	second part of time\n"
					"	tz	time zone\n"
					"	tzf	flag of time zone(- or +)\n"
					"	tzh	hour of time zone\n"
					"	tzm	minute of time zone\n"
					"	m	method\n"
					"	l	url (not implement yet)\n"
					"	lp	path part of url\n"
					"	P(k)	parameter \"k\"\n"
					"	la	anchor part of url\n"
					"	p	protocol\n"
					"	s	status code\n"
					"	b	response bytes\n"
					"	r	referer\n"
					"	a	user agent\n"
					"\nexpression operators:\n"
					"	=	equals\n"
					"	[	starts with\n"
					"	*	contains\n"
					"	]	ends with\n"
					"	&	and\n"
					"	|	or\n"
					"	!	not\n"
					"	(,)	change priority\n"
					"\nformat place holder:\n"
					"	mainly the same as filter variables but add a leader \"%%\"\n"
					"	for function P(k), directly use %%(k)\n"
					"\nlog file:\n"
					"	the log file(s) you want to analyze.\n"
					"	leave it blank if you want to read logs from standard input,\n"
					"	thus you can use pipe line to combine more tools, e.g. tail ...|awl ...\n"
					"\nexamples:\n"
					"	awl -q csv '{m=\"GET\"&!(s=200):%%h, %%s, \"%%l\"}' example.com-access_log\n"
					"	tail -f example.com-access_log | awl '{P(user):%%h, \"%%(user)\"}'\n"
					"\nbug report:\n"
					"	haifeng.813@gmail.com\n"
					"\n", cmd);
}

int main(int argc, char **argv) {
	AnalyzerParam *param = parse_param(argc, argv);
	if (param == NULL || param->show_help) {
		analyzer_usage(argv[0]);
		if (param != NULL)
			destroy_param(param);
		return 0;
	}
	analyzer_init(param);
	analyzer_analyse(param);
	analyzer_destroy(param);
	destroy_param(param);
	return 0;
}

